package com.ruoyi.common.file;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.crypto.*;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;

@Slf4j
@Component
public class Aes256GcmEncryptor {

    // 算法常量
    private static final String ALGORITHM = "AES/GCM/NoPadding"; // GCM 模式无填充
    private static final int KEY_SIZE = 256; // 256 位密钥
    private static final int IV_SIZE = 12; // 12 字节 IV（GCM 推荐）
    private static final int TAG_SIZE = 128; // 16 字节认证标签（128 位）
    private static final int BUFFER_SIZE = 8192; // 8KB 缓冲区（处理大文件）
    private static final SecureRandom SECURE_RANDOM = new SecureRandom(); // 安全随机数生成器

    /**
     * 生成 AES-256 密钥（Base64 编码，便于存储）
     */
    public String generateKey() {
        try {
            KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
            keyGenerator.init(KEY_SIZE, SECURE_RANDOM);
            SecretKey secretKey = keyGenerator.generateKey();
            return Base64.getEncoder().encodeToString(secretKey.getEncoded());
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("AES 密钥生成失败", e);
        }
    }

    /**
     * 加密视频文件（流式处理）
     * @param inputFile 原始视频文件
     * @param outputFile 加密后的视频文件（格式：IV + 密文 + 认证标签）
     * @param base64Key Base64 编码的 AES-256 密钥
     * @param aad 附加认证数据（可选，如文件名、时间戳，增强安全性）
     */
    public void encryptFile(File inputFile, File outputFile, String base64Key, String aad) {
        try (
                InputStream inputStream = new FileInputStream(inputFile);
                OutputStream outputStream = new FileOutputStream(outputFile);
                BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream, BUFFER_SIZE);
                BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream, BUFFER_SIZE)
        ) {
            // 1. 解码 Base64 密钥
            SecretKey secretKey = new SecretKeySpec(Base64.getDecoder().decode(base64Key), "AES");

            // 2. 生成随机 IV（12 字节），并写入输出文件（解密时需读取）
            byte[] iv = new byte[IV_SIZE];
            SECURE_RANDOM.nextBytes(iv);
            bufferedOutputStream.write(iv);

            // 3. 初始化 GCM 加密器
            Cipher cipher = Cipher.getInstance(ALGORITHM);
            GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(TAG_SIZE, iv);
            cipher.init(Cipher.ENCRYPT_MODE, secretKey, gcmParameterSpec);

            // 4. 可选：更新 AAD（附加认证数据，不加密但参与完整性校验）
            if (aad != null && !aad.isEmpty()) {
                cipher.updateAAD(aad.getBytes(StandardCharsets.UTF_8));
            }

            // 5. 流式加密（分段读取、加密、写入）
            byte[] buffer = new byte[BUFFER_SIZE];
            int bytesRead;
            while ((bytesRead = bufferedInputStream.read(buffer)) != -1) {
                byte[] encryptedBytes = cipher.update(buffer, 0, bytesRead);
                if (encryptedBytes != null && encryptedBytes.length > 0) {
                    bufferedOutputStream.write(encryptedBytes);
                }
            }

            // 6. 完成加密，获取认证标签并写入文件（最后拼接）
            byte[] tag = cipher.doFinal();
            bufferedOutputStream.write(tag);

        } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
                 | InvalidAlgorithmParameterException | IOException | IllegalBlockSizeException
                 | BadPaddingException e) {
            log.info("遇到了一些异常:",e);
            throw new RuntimeException("视频加密失败", e);
        }
    }

    /**
     * 解密视频文件（流式处理）
     * @param encryptedFile 加密后的视频文件（格式：IV + 密文 + 认证标签）
     * @param outputFile 解密后的原始视频文件
     * @param base64Key Base64 编码的 AES-256 密钥
     * @param aad 附加认证数据（需与加密时一致，否则解密失败）
     */
    public void decryptFile(File encryptedFile, File outputFile, String base64Key, String aad) {
        try (
                InputStream inputStream = new FileInputStream(encryptedFile);
                OutputStream outputStream = new FileOutputStream(outputFile);
                BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream, BUFFER_SIZE);
                BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream, BUFFER_SIZE)
        ) {
            // 1. 解码 Base64 密钥
            SecretKey secretKey = new SecretKeySpec(Base64.getDecoder().decode(base64Key), "AES");

            // 2. 读取 IV（前 12 字节）
            byte[] iv = new byte[IV_SIZE];
            if (bufferedInputStream.read(iv) != IV_SIZE) {
                throw new IOException("加密文件格式错误：IV 读取失败");
            }

            // 3. 计算密文长度（总长度 - IV 长度 - 标签长度）
            long totalLength = encryptedFile.length();
            long ciphertextLength = totalLength - IV_SIZE - (TAG_SIZE / 8);
            if (ciphertextLength <= 0) {
                throw new IOException("加密文件格式错误：密文长度无效");
            }

            // 4. 初始化 GCM 解密器
            Cipher cipher = Cipher.getInstance(ALGORITHM);
            GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(TAG_SIZE, iv);
            cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmParameterSpec);

            // 5. 可选：更新 AAD（需与加密时完全一致）
            if (aad != null && !aad.isEmpty()) {
                cipher.updateAAD(aad.getBytes(StandardCharsets.UTF_8));
            }

            // 6. 流式解密（分段读取密文、解密、写入）
            byte[] buffer = new byte[BUFFER_SIZE];
            long remaining = ciphertextLength;
            while (remaining > 0) {
                int bytesToRead = (int) Math.min(buffer.length, remaining);
                int bytesRead = bufferedInputStream.read(buffer, 0, bytesToRead);
                if (bytesRead == -1) {
                    break;
                }
                byte[] decryptedBytes = cipher.update(buffer, 0, bytesRead);
                if (decryptedBytes != null && decryptedBytes.length > 0) {
                    bufferedOutputStream.write(decryptedBytes);
                }
                remaining -= bytesRead;
            }

            // 7. 读取认证标签并验证（最后 16 字节）
            byte[] tag = new byte[TAG_SIZE / 8];
            if (bufferedInputStream.read(tag) != tag.length) {
                throw new IOException("加密文件格式错误：认证标签读取失败");
            }

            // 8. 完成解密（验证标签，若标签不匹配会抛出 BadPaddingException）
            byte[] finalDecryptedBytes = cipher.doFinal(tag);
            if (finalDecryptedBytes != null && finalDecryptedBytes.length > 0) {
                bufferedOutputStream.write(finalDecryptedBytes);
            }

        } catch (BadPaddingException e) {
            // 认证标签不匹配（文件被篡改或密钥/AAD 错误）
            log.info("遇到了一些异常:",e);
            throw new RuntimeException("视频解密失败：文件被篡改或密钥/AAD 错误", e);
        } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
                 | InvalidAlgorithmParameterException | IOException | IllegalBlockSizeException e) {
            log.info("遇到了一些异常:",e);
            throw new RuntimeException("视频解密失败", e);
        }
    }
}
